package controller;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.GregorianCalendar;
import java.util.Locale;
import javax.imageio.ImageIO;
import model.Model_Config;
import model.Model_Image_Line;
import model.Model_Image_Original;
import org.jgap.Chromosome;
import org.jgap.FitnessFunction;
import org.jgap.IChromosome;
import org.jgap.impl.CompositeGene;

/**
 * Controller-Klasse zur Steuerung der Operationen, die direkt mit der Evolution
 * zusammenhängen. Erbt von org.jgap.FitnessFunction, ist also die Fitnessfunktion
 * der Evolution.
 */
public class Controller_Fitness extends FitnessFunction {

    private model.Model_Image_Original image_original;
    private model.Model_Image_Line image_line;
    private int[][] originalArray;
    private int[][] lineArray;
    private int[][] maskArray;
    private boolean logimage=false;
    private int loginterval=1000;
    private String time="", imgnr_name;
    private File session_dir;
    private boolean origimgsaved=false;
    private int imgnr = 0;

    /**
     * Konstruktor für den Controller_Fitness. Übergeben werdem müssen die Models 
     * für die Einstellungen, das originalbild und das Strichbild.
     * @param config Model für die Einstellungen
     * @param image_original Model für das originalbild
     * @param image_line Model für das Strichbild (beinhaltet die Evolutionsspezifischen Daten)
     */
    public Controller_Fitness(Model_Config config, Model_Image_Original image_original, Model_Image_Line image_line) {
        this.image_original = image_original;
        this.image_line = image_line;
    }

    /**
     * Lässt die Evolution eine Generation entwickeln.
     * @param evolutionStep Nummer der Generation - es wird jedes zehnte Bild gezeichnet
     */
    public void runEvolution(int evolutionStep) {
        image_line.doEvolutionStep();
        if (evolutionStep % 10 == 0) { //zeichnet nur jedes zehnte Bild
            BufferedImage fittestImage = createLineImage((Chromosome) image_line.getGenotype().getFittestChromosome());
            image_line.setFittestImage(fittestImage);
            if(logimage && (evolutionStep % loginterval == 0)){
               logImage(fittestImage);
            }
        }
    }
    
    /**
     * speichert jedes 1000. bild im unterverzeichnis als protokoll
     * @param logimage
     * @param nr
     */
    private void logImage(BufferedImage logimage) {
        imgnr_name = "00000"+imgnr++;
        imgnr_name = imgnr_name.substring(imgnr_name.length()-5, imgnr_name.length());
        try {
//            if(!log_dir.exists()){
//                log_dir.mkdir();
//            }
            if(!session_dir.exists()){
                session_dir.mkdirs();
            }
            if(!origimgsaved){
                origimgsaved = true;
                ImageIO.write(image_original.getImage_original(), "png", new File(session_dir.getCanonicalPath()+"/original.png"));
            }
            ImageIO.write(logimage, "png", new File(session_dir.getCanonicalPath()+"/" +imgnr_name+ ".png"));
        }
        catch(Exception ignore){}
    }

    /**
     * Methode, die den Fitnesswert eines Chromosoms errechnet und zurückgibt.
     * Dabei wird jedes Pixel mit dem entsprechenden Pixel des Originalbildes verglichen 
     * und der Unterschied in der Farbe als Zahlenwert über das gesamte Bild
     * aufsummiert und zurückgegeben.
     * Eine eventulle Gewichtung wird durch die Gewichtungsmaske beruecksichtigt.
     * @param ic Das Chromosom für den der Fitnesswert berechnet werden soll.
     * @return Fitnesswert des Chromosoms
     */
    @Override
    protected double evaluate(IChromosome ic) {
        Chromosome chrom = (Chromosome) ic;
        image_line.setActualImage(createLineImage(chrom));
        lineArray = image_line.getGrayValues();
        double diff = 0, fit;
        double weight = 1D;
        originalArray = image_original.getGrayValues();
        maskArray = image_original.getGrayValuesMaske();
        for (int x = 0; x < originalArray.length; x++) {
            for (int y = 0; y < originalArray[0].length; y++) {
                if (maskArray[x][y] > 200) {
                    weight = 2D;
                } else if (maskArray[x][y] > 100) {
                    weight = 1.5D;
                }
                diff += Math.pow(originalArray[x][y] - lineArray[x][y], 2) * weight;
            }
        }
        fit = Math.sqrt(diff);
        return fit;
    }

    /**
     * erstellt ein Bild aus dem übergebenen Chromosom
     * @param chrom das Chromosom, das visualisiert werden soll 
     * @return das visualisierte Strichbild
     */
    private BufferedImage createLineImage(Chromosome chrom) {
        CompositeGene gen;
        int x1, y1, x2, y2;
        double stroke;
        float composite;
        BufferedImage image = new BufferedImage(image_line.getWidth(), image_line.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        Graphics2D g2d = image.createGraphics();

        //Hintergrund: weiß
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, image_line.getWidth(), image_line.getHeight());

        //Vordergrund (Striche): schwarz
        g2d.setColor(Color.black);

        for (int i = 0; i < chrom.getGenes().length; i++) {
            gen = (CompositeGene) chrom.getGene(i);
            x1 = (Integer) gen.geneAt(0).getAllele();
            y1 = (Integer) gen.geneAt(1).getAllele();
            x2 = (Integer) gen.geneAt(2).getAllele();
            y2 = (Integer) gen.geneAt(3).getAllele();
            stroke = (Integer) gen.geneAt(4).getAllele();
            composite = (((Integer) gen.geneAt(5).getAllele()) / 100f);
            //Setzt die Transparenz der Striche und sorgt dafür, dass mehrere Striche übereinander sich in der Farbe verstärken            
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, composite));
            g2d.setStroke(new BasicStroke((float) stroke));
            g2d.draw(new Line2D.Double(x1, y1, x2, y2));
        }
        g2d.dispose();
        return image;
    }

    protected void logimage(view.GUI gui) {
        gui.logname(logimage);
        logimage = !logimage;
        logtime();
        session_dir = new File("./imagelog"+"/"+time+"_"+gui.getLogName());
        origimgsaved = false;
        imgnr = 0;
    }

    protected void setloginterval(int value) {
        loginterval = value;
    }
    protected void logtime(){
        GregorianCalendar c = new GregorianCalendar(Locale.GERMAN);
        time = ""+c.get(GregorianCalendar.DATE)+"."+(c.get(GregorianCalendar.MONTH)+1)+"."+c.get(GregorianCalendar.YEAR);
        time+=" "+c.get(GregorianCalendar.HOUR_OF_DAY)+"-"+c.get(GregorianCalendar.MINUTE)+"-"+c.get(GregorianCalendar.SECOND);
    }
}
